package com.example.base_model.network.interceptor

import com.blankj.utilcode.util.LogUtils
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types.newParameterizedType
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okio.Buffer
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.util.concurrent.TimeUnit
import javax.inject.Inject

/**
 * @Author: jiangKunKun
 * @CreateDate: 2025/7/14
 * @Description: 一个用于OkHttp的拦截器，可以优雅地打印网络请求和响应的详细日志。
 *               支持不同的日志级别，并能将JSON格式的请求头、参数和响应体进行美化输出。
 * @SpittingGrooves
 */
class HttpLoggingInterceptor @Inject constructor(
    private val moshi: Moshi // 通过依赖注入获取Moshi实例，用于JSON的序列化和反序列化
) : Interceptor {

    /**
     * 定义日志打印的级别。
     */
    enum class Level {
        /** 不打印任何日志。 */
        NONE,

        /**
         * 打印请求和响应的基本信息。
         * 包括：请求URL、请求方法、请求头、GET参数、请求体、响应码、响应信息、请求耗时、响应结果。
         * 不会打印响应头。
         */
        BASIC,

        /** 打印所有请求和响应的详细信息，包括BASIC级别的所有内容以及响应头。 */
        ALL
    }

    /**
     * 当前拦截器的日志级别，使用 @Volatile 保证多线程环境下的可见性。
     * 默认为 BASIC。可以动态修改此值来控制日志输出。
     */
    @Volatile
    var level: Level = Level.BASIC


    // 使用 lazy 延迟初始化Moshi适配器，可以提前获取并复用，以提高性能。
    // 这个适配器专门用于处理 Map<String, Any?> 类型的对象，常用于请求头和GET参数的格式化。
    private val mapOfStringAnyAdapter: JsonAdapter<Map<String, Any?>> by lazy {
        val type = newParameterizedType(Map::class.java, String::class.java, Any::class.java)
        moshi.adapter(type)
    }

    // 这个适配器用于处理任意类型的对象，具备更强的通用性。
    // serializeNulls() 确保在序列化为JSON时，null值也会被保留，而不是被忽略。
    private val anyAdapter: JsonAdapter<Any> by lazy {
        moshi.adapter(Any::class.java).serializeNulls()
    }

    /**
     * 实现Interceptor接口的核心方法，在此处拦截网络请求和响应。
     */
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        // 如果日志级别为NONE，则直接继续请求链，不执行任何日志操作。
        if (level == Level.NONE) {
            return chain.proceed(request)
        }

        // 执行请求，并获取响应。只调用一次 proceed，并将结果用于日志记录和返回。
        val response = chain.proceed(request)

        // 为了读取响应体内容，我们需要先克隆它，因为响应体（ResponseBody）是一次性的流，读取后即被消耗。
        val responseBody = response.body
        val responseString = responseBody?.string() // 读取响应体为字符串

        // 调用日志打印方法
        logRequest(request, response, responseString)

        // 由于上面的 `responseBody.string()` 已经消耗了原始的响应体流，
        // 我们必须根据刚才读取的字符串内容，重新创建一个新的响应体，并把它设置回响应对象中，
        // 这样调用链中后续的拦截器或最终的调用者才能正常解析响应数据。
        val contentType = responseBody?.contentType()
        val newResponseBody = ResponseBody.create(contentType, responseString ?: "")

        return response.newBuilder().body(newResponseBody).build()
    }

    /**
     * 核心日志打印方法，负责组装和输出所有网络请求和响应的信息。
     * @param request 原始请求对象。
     * @param response 响应对象。
     * @param responseString 从响应体中读取的字符串内容。
     */
    private fun logRequest(request: Request, response: Response, responseString: String?) {
        val sb = StringBuffer() // 使用StringBuffer拼接日志字符串，效率较高
        val startNs = System.nanoTime() // 记录请求开始时间（纳秒）

        sb.append("请求地址: ${request.url}\n")
        sb.append("请求模式: ${request.method}\n")

        // 打印请求头 (BASIC 和 ALL 级别)
        if (level == Level.BASIC || level == Level.ALL) {
            val headers = request.headers
            if (headers.size == 0) {
                sb.append("请求头: {}\n")
            } else {
                val headerMap = headers.toMultimap().mapValues { it.value.joinToString() }
                // 尝试将请求头Map格式化为美观的JSON字符串
                try {
                    val headerJsonString = mapOfStringAnyAdapter.indent("  ").toJson(headerMap)
                    sb.append("请求头: $headerJsonString\n")
                } catch (e: Exception) {
                    sb.append("请求头: ${headerMap.toString()} (格式化失败)\n") // 格式化失败则直接打印Map
                }
            }
        }

        // 打印GET请求的参数 (BASIC 和 ALL 级别)
        if ((level == Level.BASIC || level == Level.ALL) && request.method == "GET") {
            val url = request.url
            val getParams = extractGetParams(url)
            if (getParams.isEmpty()) {
                sb.append("GET 参数: {}\n")
            } else {
                try {
                    val paramsJsonString = mapOfStringAnyAdapter.indent("  ").toJson(getParams)
                    sb.append("GET 参数: $paramsJsonString\n")
                } catch (e: Exception) {
                    sb.append("GET 参数: ${getParams.toString()} (格式化失败)\n")
                }
            }
        }

        // 打印请求体 (POST, PUT等) (BASIC 和 ALL 级别)
        if ((level == Level.BASIC || level == Level.ALL) && request.body != null) {
            val requestBody = request.body!!
            val contentType = requestBody.contentType()
            // 判断是否是文件上传等多部分请求
            val isMultipart = contentType?.toString()?.contains("multipart") == true

            // 添加请求体类型标识
            when {
                contentType?.toString()?.contains("application/json") == true -> {
                    sb.append("请求体类型: JSON 数据\n")
                }
                contentType?.toString()?.contains("application/x-www-form-urlencoded") == true -> {
                    sb.append("请求体类型: Form 表单\n")
                }
                isMultipart -> {
                    sb.append("请求体类型: Multipart 文件上传\n")
                }
                else -> {
                    sb.append("请求体类型: ${contentType?.toString() ?: "未知"}\n")
                }
            }

            if (!isMultipart) {
                val buffer = Buffer()
                requestBody.writeTo(buffer)

                val charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8
                // 检查请求体是否为可读的纯文本
                if (isPlaintext(buffer)) {
                    val bodyString = buffer.readString(charset)
                    sb.append("Body: ${formatJson(bodyString)}\n") // 格式化JSON后打印
                } else {
                    // 如果是二进制数据（如文件），则不打印内容，只提示大小
                    sb.append("Body: (binary ${requestBody.contentLength()}-byte body omitted)")
                }
            } else {
                // 如果是多部分请求，不打印具体内容
                sb.append("Body: (multipart body omitted)\n")
            }
        }

        val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) // 计算请求耗时（毫秒）
        sb.append("HTTP code: ${response.code}\n")
        sb.append("HTTP message: ${response.message}\n")
        sb.append("HTTP tookMs: (${tookMs}ms)\n")

        // 打印响应头 (仅 ALL 级别)
        if (level == Level.ALL) {
            val headers = response.headers
            sb.append("响应头\n")
            for (i in 0 until headers.size) {
                sb.append("${headers.name(i)}: ${headers.value(i)}\n")
            }
        }

        // 打印响应体 (BASIC 和 ALL 级别)
        if ((level == Level.BASIC || level == Level.ALL) && response.body != null) {
            val responseBody = response.body!!
            val isMultipart = responseBody.contentType()?.toString()?.contains("multipart") == true

            if (!isMultipart) {
                val charset = responseBody.contentType()?.charset(StandardCharsets.UTF_8)
                    ?: StandardCharsets.UTF_8

                // 检查响应体内容是否为非空且可读的纯文本
                if (!responseString.isNullOrEmpty() && isPlaintext(Buffer().writeString(responseString, charset))) {
                    sb.append("结果: ${formatJson(responseString)}\n") // 格式化JSON后打印
                } else {
                    sb.append("结果: (内容长度-> ${responseBody.contentLength()}-非文本! \n响应体的内容不是可读文本（如图片、文件流、加密数据等）)\n")
                }
                sb.append("END HTTP\n")
            } else {
                sb.append("结果: 多段内容已省略 (用于文件上传/下载返回的数据，结构复杂不适合直接打印, 响应头中包含 Content-Type: multipart/*)\n")
            }
        }
        // 使用LogUtils工具类输出最终拼接好的日志字符串
        LogUtils.d(sb.toString())
    }

    /**
     * 使用 Moshi 将一个字符串格式化为带缩进的 JSON 字符串。
     * 如果输入的字符串不是有效的JSON格式，则会捕获异常并返回原始字符串。
     * @param data 可能为JSON格式的字符串。
     * @return 格式化后的JSON字符串或原始字符串。
     */
    private fun formatJson(data: String?): String {
        if (data.isNullOrBlank()) return data ?: "null"

        return try {
            // 1. 将输入的字符串解析为通用的对象 (Map, List, etc.)
            val parsedJsonAsObject: Any? = anyAdapter.fromJson(data)
            // 2. 将解析后的对象再序列化回带缩进的美观JSON字符串
            anyAdapter.indent("  ").toJson(parsedJsonAsObject)
        } catch (e: Exception) {
            // 如果字符串不是有效的JSON格式，Moshi会抛出异常。此时，我们直接返回原始字符串。
            data
        }
    }


    /**
     * 判断Okio.Buffer中的数据是否为纯文本。
     * 通过检查数据前缀的一小部分字节，判断是否包含非法的控制字符。
     * @param buffer 包含待检查数据的Buffer。
     * @return 如果是纯文本则返回true，否则返回false。
     */
    private fun isPlaintext(buffer: Buffer): Boolean {
        try {
            val prefix = Buffer()
            // 复制buffer中最多前64个字节到prefix中进行检查
            val byteCount = minOf(64, buffer.size)
            buffer.copyTo(prefix, 0, byteCount)
            for (i in 0 until 16) {
                if (prefix.exhausted()) break
                val codePoint = prefix.readUtf8CodePoint()
                // 如果字符是ISO控制字符但又不是空白字符（如换行、制表符），则认为不是纯文本
                if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
                    return false
                }
            }
            return true
        } catch (e: Exception) {
            // 发生异常（如UTF-8解码失败）时，安全起见，返回false
            return false
        }
    }

    /**
     * 从HttpUrl中提取GET请求的所有查询参数。
     * 此方法会尝试将参数值解析为JSON对象或数组，如果失败则保留为原始字符串。
     * @param url 请求的URL。
     * @return 一个包含所有参数的Map，键为参数名，值为解析后的对象或原始字符串。
     */
    private fun extractGetParams(url: HttpUrl): Map<String, Any?> {
        val params = mutableMapOf<String, Any?>()
        for (name in url.queryParameterNames) {
            url.queryParameterValues(name).forEach { value ->
                if (value != null) {
                    // 尝试将参数值（value）当作JSON字符串进行解析
                    val parsedValue = try {
                        anyAdapter.fromJson(value)
                    } catch (e: Exception) {
                        value // 如果解析失败，说明它不是一个JSON字符串，保留原始值
                    }
                    params[name] = parsedValue
                }
            }
        }
        return params
    }
}
